using System.Collections.Generic;
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Serilog;
using Serilog.Events;
using Infrastructure.Log;

namespace Infrastructure
{
    /// <summary>
    /// 日志操作的辅助类。
    /// appsettings.json配置文中可以设置当前项目信息,Project作为配置项。
    /// 日记级别从appsettings.json配置文件读取,Logging:LogLevel:Default作为日志级别的配置。
    /// 读取失败或无此配置项,则默认为Information。
    /// appsettings.json配置文中可以设置日志是否发布到日志系统,Logging:LogCenter:Enabled作为配置项。
    /// 读取失败或无此配置项,则默认为false。
    /// 无论是否发布,都将记录文本日志。
    /// </summary>
    public static class LogHelper
    {
        /// <summary>
        /// 错误日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="assembly">记录程序集的名称,可以是调用方的程序集,也可以是抛出异常的程序集,
        /// 可用对象.GetType().AssemblyQualifiedName属性获取</param>
        /// <param name="needStack">是否需要记录堆栈信息,仅在 <paramref name="ex"/> 为空时设置为false有效。</param>
        public static async Task Error(string message, Exception ex, string addr, string user, string submitData,
            string assembly, bool needStack = true)
        {
            await SaveLog("Error", message, ex, addr, user, submitData, assembly, needStack, true);
        }

        /// <summary>
        /// 错误日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        public static async Task Error(string message)
        {
            await SaveLog("Error", message, null, null, null, null, null, false, false);
        }

        /// <summary>
        /// 错误日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        public static async Task Error(string message, Exception ex)
        {
            await SaveLog("Error", message, ex, null, null, null, null, true, false);
        }

        /// <summary>
        /// 调试日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="assembly">记录程序集的名称,可以是调用方的程序集,也可以是抛出异常的程序集,
        /// 可用对象.GetType().AssemblyQualifiedName属性获取</param>
        /// <param name="needStack">是否需要记录堆栈信息,仅在 <paramref name="ex"/> 为空时设置为false有效。</param>
        public static async Task Debug(string message, Exception ex, string addr, string user, string submitData,
            string assembly, bool needStack = true)
        {
            await SaveLog("Debug", message, ex, addr, user, submitData, assembly, needStack, true);
        }

        /// <summary>
        /// 调试日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        public static async Task Debug(string message)
        {
            await SaveLog("Debug", message, null, null, null, null, null, false, false);
        }

        /// <summary>
        /// 调试日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        public static async Task Debug(string message, Exception ex)
        {
            await SaveLog("Debug", message, ex, null, null, null, null, true, false);
        }

        /// <summary>
        /// 致命错误日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="assembly">记录程序集的名称,可以是调用方的程序集,也可以是抛出异常的程序集,
        /// 可用对象.GetType().AssemblyQualifiedName属性获取</param>
        /// <param name="needStack">是否需要记录堆栈信息,仅在 <paramref name="ex"/> 为空时设置为false有效。</param>
        public static async Task Fatal(string message, Exception ex, string addr, string user, string submitData,
            string assembly, bool needStack = true)
        {
            await SaveLog("Fatal", message, ex, addr, user, submitData, assembly, needStack, true);
        }

        /// <summary>
        /// 致命错误日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        public static async Task Fatal(string message)
        {
            await SaveLog("Fatal", message, null, null, null, null, null, false, false);
        }

        /// <summary>
        /// 致命错误日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        public static async Task Fatal(string message, Exception ex)
        {
            await SaveLog("Fatal", message, ex, null, null, null, null, true, false);
        }

        /// <summary>
        /// 信息日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="needStack">是否需要记录堆栈信息,记录的堆栈信息为调用本方法的堆栈。</param>
        public static async Task Information(string message, string addr, string user, string submitData,
            bool needStack = true)
        {
            await SaveLog("Information", message, null, addr, user, submitData, string.Empty, needStack, true);
        }

        /// <summary>
        /// 信息日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        public static async Task Information(string message)
        {
            await SaveLog("Information", message, null, null, null, null, null, false, false);
        }

        /// <summary>
        /// 警告日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="needStack">是否需要记录堆栈信息,记录的堆栈信息为调用本方法的堆栈。</param>
        public static async Task Warning(string message, string addr, string user, string submitData,
            bool needStack = true)
        {
            await SaveLog("Warning", message, null, addr, user, submitData, string.Empty, needStack, true);
        }

        /// <summary>
        /// 警告日志。
        /// </summary>
        /// <param name="message">日志信息</param>
        public static async Task Warning(string message)
        {
            await SaveLog("Warning", message, null, null, null, null, null, false, false);
        }

        #region Private Methods

        /// <summary>
        /// 将字符串转换为日志级别
        /// </summary>
        /// <param name="levelStr">级别字符串</param>
        /// <returns></returns>
        private static LogEventLevel ConvertToLevel(string levelStr)
        {
            LogEventLevel level = LogEventLevel.Information;

            if (!string.IsNullOrEmpty(levelStr))
            {
                switch (levelStr.ToLower())
                {
                    case "verbose":
                        level = LogEventLevel.Verbose;
                        break;
                    case "debug":
                        level = LogEventLevel.Debug;
                        break;
                    case "information":
                        level = LogEventLevel.Information;
                        break;
                    case "warning":
                        level = LogEventLevel.Warning;
                        break;
                    case "error":
                        level = LogEventLevel.Error;
                        break;
                    case "fatal":
                        level = LogEventLevel.Fatal;
                        break;
                    default:
                        break;
                }
            }

            return level;
        }

        /// <summary>
        /// 得到日志对象。
        /// 日记级别默认为Information。
        /// 可使用appsettings.json作为配置文件,Logging:LogLevel:Default作为日志级别的配置。
        /// </summary>
        /// <returns>日志对象</returns>
        private static ILogger GetLogger()
        {
            if (Logger == null)
            {
                string path = PathHelper.GetBaseDirectory();
                Logger = new LoggerConfiguration()
                    .MinimumLevel.Is(MinLevel)
                    .WriteTo.Async(p => p.File($"{path}/logs/.log", rollingInterval: RollingInterval.Month, shared: true)) //以小时为记录单位
                    .CreateLogger();
            }

            return Logger;
        }

        /// <summary>
        /// 保存日志
        /// </summary>
        /// <param name="levelStr">日志消息级别</param>
        /// <param name="message">日志信息</param>
        /// <param name="ex">发生的异常</param>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="assembly">记录程序集的名称,可以是调用方的程序集,也可以是抛出异常的程序集,
        /// 可用对象.GetType().AssemblyQualifiedName属性获取</param>
        /// <param name="needStack">是否需要记录堆栈信息,仅在 <paramref name="ex"/> 为空时设置为false有效。</param>
        /// <param name="haslogNO">是否需要生成日志的消息编号。</param>
        private static async Task SaveLog(string levelStr, string message, Exception ex,
            string addr, string user, string submitData, string assembly, bool needStack,
            bool haslogNO)
        {
            // 转换为日志级别
            LogEventLevel level = ConvertToLevel(levelStr);

            // 与最低级别进行比较
            if ((int)level < (int)MinLevel)
                return;

            IEnumerable<StackFrame> frames = null;

            // 获取调用帧,如果传入了ex对象,则获取ex的调用帧(即发生异常的栈帧),,此时只获取有行号的帧
            // 否则获取调用日志记录方法的栈帧(即什么方法调用的日志记录方法),此时只获取有行号的前20帧
            // 避免堆栈信息过多
            if (ex != null)
                frames = new StackTrace(ex, true).GetFrames().Where(p => p.GetFileLineNumber() > 0);
            else if (needStack)
                frames = new StackTrace(true).GetFrames()
                    .Where(p => p.GetFileLineNumber() > 0)
                    .Skip(2)
                    .Take(20);

            // 准备日志数据
            BackendLog log = SetLog(addr, user, message, submitData, assembly, frames, ex, haslogNO);

            // 记录文本日志
            TextLog(log, level);


            try
            {
                // 封装后端日志信息
                log.CreateByDate = DateTime.Now;
                log.Level = levelStr;

                //if (InsertLog.Contains(levelStr))
                //{
                //    //记录日志进入数据库
                //    await Task.Run(() =>
                //    {
                //        SqlDapper.Insert<BackendLog>(log);
                //    });
                //}
            }
            catch (Exception e)
            {
                GetLogger().Write(LogEventLevel.Error, e, "异常信息");
            }
        }

        /// <summary>
        /// 包装日志对象
        /// </summary>
        /// <param name="addr">日志来源地址,一般为IP地址</param>
        /// <param name="user">日志来源的操作用户,一般前端登录系统后有用户信息,后端一般没有用户信息。</param>
        /// <param name="message">日志信息</param>
        /// <param name="submitData">提交数据,前端提交到后端的数据,或者后端接收到的数据。</param>
        /// <param name="assembly">记录程序集的名称,可以是调用方的程序集,也可以是抛出异常的程序集,
        /// 可用对象.GetType().AssemblyQualifiedName属性获取</param>
        /// <param name="stackFrames">调用栈帧</param>
        /// <param name="ex">发生的异常</param>
        /// <param name="haslogNO">是否需要生成日志的消息编号。</param>
        /// <returns>一个包装的 <see cref="BackendLog"/> 对象.</returns>
        private static BackendLog SetLog(string addr, string user, string message,
            string submitData, string assembly, IEnumerable<StackFrame> stackFrames, Exception ex,
            bool haslogNO)
        {
            // 封装后端日志信息
            BackendLog log = new BackendLog()
            {
                Project = Project,
                IP = addr,
                User = user,
                Message = message,
                Stack = GetStackTrace(stackFrames, ex),
                SubmitData = submitData,
                Assembly = assembly
            };

            if (haslogNO)
                log.LogNO = GenerateLogNO();

            return log;
        }

        /// <summary>
        /// 格式化调用的堆栈信息。
        /// 返回字符串格式如下:
        /// {异常类型}: {异常信息}
        ///  {内部异常类型}: {内部异常信息}
        ///   {内部异常类型}: {内部异常信息}...
        /// at 方法名(参数名...) in 文件名:line 行号,col 列号
        /// at 方法名(参数名...) in 文件名:line 行号,col 列号
        /// at 方法名(参数名...) in 文件名:line 行号,col 列号
        /// 示例如下:
        /// <example>
        /// System.Exception: 发生了一个异常.
        ///    System.Exception: inner 3
        ///       System.Exception: inner 2
        ///          System.Exception: inner 1
        /// at Blossim.Business.Lib.IdHelper.Validate(IEnumerable`1 data, List`1 ids) in IdHelper.cs:line 29,col 13
        /// at Blossim.Business.CutterBusiness.AtestMethod() in Cutter.cs:line 283,col 13
        /// at Blossim.Business.CutterBusiness.InDevice(BusinessData businessData) in Cutter.cs:line 301,col 13
        /// at WebApi.Lib.BusinessHelper.Execute(ApiData apiData, IRepository repo) in BusinessHelper.cs:line 129,col 21
        /// </example>
        /// </summary>
        /// <param name="stackFrames">调用栈帧</param>
        /// <param name="ex">异常对象</param>
        /// <returns>堆栈信息字符串</returns>
        private static string GetStackTrace(IEnumerable<StackFrame> stackFrames, Exception ex)
        {
            if (ex == null) { return ""; }
            StringBuilder sb = new StringBuilder();

            // 记录异常信息
            sb.Append(ex.Message);

            if (stackFrames == null || !stackFrames.Any())
                return sb.ToString();

            // 处理堆栈信息
            foreach (StackFrame frame in stackFrames)
            {
                if (frame == null)
                    continue;

                if (sb.Length > 0)
                    sb.AppendLine();

                sb.Append("      ");

                // 记录调用方法
                try
                {
                    //if (frame.HasMethod())
                    //{
                    // 记录方法的完整名称
                    sb.Append("at ");

                    // 某些方法无法获取声明类型
                    if (frame.GetMethod().DeclaringType != null)
                        sb.Append(frame.GetMethod().DeclaringType.FullName);

                    sb.Append($".{frame.GetMethod().Name}(");

                    // 记录方法的参数信息
                    var parameters = frame.GetMethod().GetParameters();

                    if (parameters != null && parameters.Any())
                    {
                        // 记录每个参数的类型与名称
                        foreach (ParameterInfo parameter in parameters)
                        {
                            sb.Append(parameter.ParameterType.Name);
                            sb.Append($" {parameter.Name}, ");
                        }

                        // 移除最后一个参数末尾的','
                        sb = sb.Remove(sb.Length - 1, 1);
                    }

                    sb.Append(")");
                    //}
                }
                catch (Exception)
                {

                    throw;
                }

                // 记录执行文件
                sb.Append(" in ");

                // 这里只记录文件名称,不记录完整的文件路径
                // 如未获取到文件信息,则默认为调用方法名称
                if (!string.IsNullOrEmpty(frame.GetFileName()))
                {
                    int index = frame.GetFileName().LastIndexOf("\\");
                    sb.Append(frame.GetFileName().Substring(index + 1));
                }
                else
                    sb.Append(frame.GetMethod().Name);

                // 记录抛出异常的行列
                sb.Append($":line {frame.GetFileLineNumber()},col {frame.GetFileColumnNumber()}");
            }

            return sb.ToString();

        }

        /// <summary>
        /// 记录文本日志,文本日志不记录Project。
        /// </summary>
        /// <param name="log">一个 <see cref="BackendLog"/> 对象</param>
        /// <param name="level">日志级别</param>
        private static void TextLog(BackendLog log, LogEventLevel level)
        {
            StringBuilder sb = new StringBuilder();

            // Message为必有的信息,其他为可选信息
            if (!string.IsNullOrEmpty(log.Message))
                sb.Append($"{log.Message}");
            else
                sb.Append($"No message.");

            if (!string.IsNullOrEmpty(log.Assembly))
            {
                sb.AppendLine();
                sb.Append($"   Assembly: {log.Assembly}");
            }

            if (!string.IsNullOrEmpty(log.LogNO))
            {
                sb.AppendLine();
                sb.Append($"   LogNO: {log.LogNO}");
            }

            if (!string.IsNullOrEmpty(log.IP))
            {
                sb.AppendLine();
                sb.Append($"   Address: {log.IP}");
            }

            if (!string.IsNullOrEmpty(log.User))
            {
                sb.AppendLine();
                sb.Append($"   User: {log.User}");
            }

            if (!string.IsNullOrEmpty(log.SubmitData))
            {
                sb.AppendLine();
                sb.Append($"   Submit data: {log.SubmitData}");
            }

            if (!string.IsNullOrEmpty(log.Stack))
            {
                sb.AppendLine();
                sb.AppendLine($"   Stack:");
                sb.Append(log.Stack);
            }

            GetLogger().Write(level, sb.ToString());

        }

        /// <summary>
        /// 生成日志编号
        /// </summary>
        /// <returns></returns>
        private static string GenerateLogNO()
        {
            // 编码源
            string source = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789";

            // 编码长度
            int length = 4;

            char[] results = new char[length];

            // 生成编码
            for (int i = 0; i < length; i++)
            {
                int index = new Random().Next(source.Length);
                results[i] = source[index];
            }

            return string.Join("", results);
        }

        #endregion

        #region Properties

        /// <summary>
        /// 日志对象
        /// </summary>
        private static ILogger Logger;

        /// <summary>
        /// 最低日志级别,从配置文件读取,读取失败默认为Infomation。
        /// </summary>
        private static LogEventLevel MinLevel  = ConvertToLevel(Config.ReadSetting("Logging:LogLevel:Default"));

        /// <summary>
        /// 当前项目
        /// </summary>
        private static string Project
        {
            get
            {
                var project = Config.ReadSetting("Project");
                return project ?? "MyDefalut";
            }
        }

        /// <summary>
        /// 当前添加日志级别
        /// </summary>
        private static string InsertLog
        {
            get
            {
                var level = Config.ReadSetting("InsertLogLevel");
                return level ?? "";
            }
        }


        //private static SqlDapper SqlDapper
        //{
        //    get { return new SqlDapper(); }
        //}

        #endregion

    }
}
